#include "mem_file.h"
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

char *filename;
FILE *f;

typedef struct file_map_element {
     bool lock;
     size_t sz;
     off_t start;
     struct file_map_element *next;
} file_map_element_t;

file_map_element_t *file_map_head;


void file_map_create(size_t sz) {
     file_map_head = (file_map_element_t *)malloc(sizeof(file_map_element_t));
     
     file_map_head->lock = false;
     file_map_head->sz = sz;
     file_map_head->start = 0;
     file_map_head->next = NULL;
}

void file_map_remove() {
     file_map_element_t *current = file_map_head;
     file_map_element_t *next;
	  
     while (current != NULL) {
	  next = current->next;
	  free(current);
	  current = next;
     }
}

off_t file_map_add(size_t sz) {
     file_map_element_t *current = file_map_head;
     
     while ((sz > current->sz)||(current->lock == true)) {
	  current = current->next;
	  if (current == NULL) return -1;
     }
     
     if (sz == current->sz) {
	  current->lock = true;
	  current->sz = sz;
	  return current->start;
     }
     
     file_map_element_t *new_element = (file_map_element_t *)malloc(sizeof(file_map_element_t));
     
     new_element->lock = false;
     new_element->sz = current->sz - sz;
     new_element->start = current->start + sz;
     new_element->next = current->next;
     
     current->lock = true;
     current->sz = sz;
     current->next = new_element;
     return current->start; 
}

off_t file_map_delete(off_t address){
     file_map_element_t *current = file_map_head;
     file_map_element_t *previous = NULL;
     
     while (current->start != address) {
	  previous = current;
	  current = current->next;
	  if (current == NULL) return 0;
     }
     
     off_t delete_size = current->sz;

     if ((current->next == NULL)||(current->next->lock == true)) {
	  if ((previous == NULL)||(previous->lock == true)) current->lock = false;

	  else {
	       previous->next = current->next;
	       previous->sz += current->sz;
	       free(current);
	  }
     }
     
     else {
	  if ((previous == NULL)||(previous->lock == true)) {
	       file_map_element_t *next = current->next;
	       current->lock = false;
	       current->sz += next->sz;
	       current->next = next->next;
	       free(next);
	  }
	  
	  else {
	       file_map_element_t *next = current->next;
	       previous->next = next->next;
	       previous->sz += current->sz + next->sz;
	       free(current);
	       free(next);
	  }
     }
     return delete_size;
}

void file_poisoning(off_t address, size_t sz) {
     off_t i;
     char c = '\00';
     
     fseeko(f, address, SEEK_SET);
     for (i = 0; i < sz; i++) fwrite(&c, 1, 1, f);
}


int file_create(size_t file_size, const char *file_name) {
     filename = (char *) malloc(strlen(file_name));
     strncpy(filename, file_name, strlen(file_name));
     
     if (access(filename, 0))
	  if (!access(filename, 4)) {
	       errno = EACCES;
	       return 0;
	  }
	  
     f = fopen(filename, "w+b");
     if (f == NULL) {
	  errno = EFAULT;
	  return 0;
     }
     
     if (posix_fallocate(fileno(f), 0, file_size) != 0) {
	  errno = EFBIG;
	  fclose(f);
	  remove(file_name);
	  return 0;
     }
     
     file_map_create(file_size);
     
     return 1;
}

void file_remove() {
     fclose(f);
     remove(filename);
     file_map_remove();
     free(filename);
}

off_t file_write_without_defragmentation(void *buf, size_t sz) {
     off_t address = file_map_add(sz);
     if (address == -1) return -1;
     
     if (buf != NULL) {
	  fseeko(f, address, SEEK_SET);
	  fwrite(buf, sz, 1, f);
     }
     
     return address;
}

void file_read(void* buf, off_t address, size_t sz) {
     fseeko(f, address, SEEK_SET);
     fread(buf, sz, 1, f);
}

void file_delete(off_t address) {
     /*size_t sz = */file_map_delete(address);
     //file_poisoning(address, sz);
}


off_t file_write_with_defragmentation(void* buf, size_t sz) {
     file_map_element_t *current = file_map_head;
     file_map_element_t *previous = NULL;
     
     while (current->lock == true) { //Находим первую дыру в файле.
	  previous = current;
	  current = current->next;
	  if (current == NULL) return -1;
     }
     
     size_t free_space = current->sz;
     file_map_element_t *reserved = current; //Оставим на потом, чтобы записать туда новый элемент.
     if (previous != NULL) previous->next = current->next;
     current = current->next;
     
     while (free_space < sz) {
	  if (current == NULL) return -1;
	  
	  void *buf;
	  while (current->lock == true) { //Все, что записано после дыры передвигается назад на ее размер.
	       buf = malloc(current->sz);
	       
	       fseeko(f, current->start, SEEK_SET);
	       fread(buf, current->sz, 1, f);
	       current->start -= free_space;
	       fseeko(f, current->start, SEEK_SET);
	       fwrite(buf, current->sz, 1, f);
	  
	       free(buf);
	  
	       previous = current;
	       current = current->next;
	       if (current == NULL) return -1;
	  }
	  
	  free_space += current->sz; //Начиная со следующей дыры сдвиг увеличивается, соответствующий элемент списка удаляется.
	  previous->next = current->next;
	  free(current);
	  current = previous->next;
     }
     
     previous->next = reserved; //Когда места хватает, вставляем зарезервированный элемент.
     reserved->next = current;
     
     reserved->start = previous->start + previous->sz;
     reserved->sz = sz;
     reserved->lock = true;
     
     if (buf != NULL) { //Записываем в файл по полученному адресу, если нас об этом просили.
	  fseeko(f, reserved->start, SEEK_SET);
	  fwrite(buf, sz, 1, f);
     }
     
     if (free_space >= sz) { //Если после вставки осталось свободное место, создаем элемент списка для дыры.
	  file_map_element_t *rest = (file_map_element_t *)malloc(sizeof(file_map_element_t));
	  reserved->next = rest;
	  rest->next = current;
	  rest->lock = false;
	  rest->sz = free_space - sz;
	  rest->start = reserved->start + sz;
     }
     return reserved->start;
}


